package com.yueya.auth.filter;

import com.yueya.auth.config.AuthProperties;
import com.yueya.auth.model.TokenDto;
import com.yueya.auth.realm.Principal;
import com.yueya.auth.service.AuthTokenService;
import com.yueya.auth.utils.JwtUtil;
import com.yueya.auth.utils.KeysUtil;
import com.yueya.auth.utils.RequestUtil;
import com.yueya.common.util.JsonMapper;
import com.yueya.common.web.RestResult;
import org.apache.shiro.authc.*;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Writer;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import static com.yueya.auth.config.AuthConstant.ATTRIBUTE_LOGIN_NAME;
import static com.yueya.auth.config.AuthConstant.LOGIN_FAIL_MESSAGE;

public class AccountFilter extends FormAuthenticationFilter {
    private AuthProperties authProperties;
    private AuthTokenService tokenService;
    private Logger logger= LoggerFactory.getLogger(getClass());
    protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
        String username = getUsername(request);
        String password = getPassword(request);
        if (password==null){
            password = "";
        }
        boolean rememberMe = isRememberMe(request);
        return new UsernamePasswordToken(username, password.toCharArray(), rememberMe);
    }

    @Override
    protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws IOException {
        //return super.onLoginSuccess(token, subject, request, response);
        if (token instanceof UsernamePasswordToken && !authProperties.isJwtMode()) {
            subject.getSession(false).setAttribute(ATTRIBUTE_LOGIN_NAME, ((UsernamePasswordToken)token).getUsername());
            onNormal(token, subject, request, response);
        } else {
            String jwtId = KeysUtil.uuid();
            Date date = new Date();
            onJwt(token,subject,jwtId,date,request,response);
            saveTokenInfo((HttpServletRequest) request,subject,jwtId,date);
        }
        return false;
    }

    private void saveTokenInfo(HttpServletRequest request, Subject subject, String jwtId, Date date) {
        Principal principal = (Principal) subject.getPrincipal();
        TokenDto dto = new TokenDto();
        dto.setCreateDate(date);
        dto.setUserId(principal.getId());
        dto.setLoginName(principal.getUserName());
        dto.setHost(RequestUtil.getRemoteAddr(request));
        dto.setAgent(RequestUtil.getUserAgent(request));
        dto.setTokenId(jwtId);
        tokenService.onCreateJwtToken(dto);
    }

    public void onNormal(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws IOException {
        Map<String,Object> data =  new HashMap<>(2);
        data.put("type",authProperties.getMode());
        String msg= JsonMapper.toJsonString(RestResult.OkWithData(data));
        response.setContentType("application/json");
        response.setCharacterEncoding("UTF-8");
        Writer writer=response.getWriter();
        writer.write(msg);
        writer.flush();
        writer.close();
    }

    public void onJwt(AuthenticationToken token, Subject subject,String jwtId, Date date, ServletRequest request, ServletResponse response) throws IOException {
        Principal principal = (Principal) subject.getPrincipal();
        String accessToken = createJwtToke(principal,jwtId, date);
        String refreshToken = createRefreshToke(principal,jwtId,date);
        Map<String,Object> data =  new HashMap<>(2);
        data.put("accessToken",accessToken);
        data.put("refreshToken",refreshToken);
        data.put("type",authProperties.getMode());
        String result = JsonMapper.toJsonString(RestResult.OkWithData(data));
        if (authProperties.isJwtMode()) {
            RequestUtil.setHeader((HttpServletRequest) request,(HttpServletResponse) response);
        }
        Writer writer=response.getWriter();
        writer.write(result);
        writer.flush();
        writer.close();
    }

    private String createJwtToke(Principal account, String jwtId, Date date) {
        return JwtUtil.createJwtToke(account.getId(),account.getUserName(),account.getRoles(),account.getPermissions(),
                jwtId,date,authProperties);
    }

    private String createRefreshToke(Principal account, String jwtId, Date date) {
        return JwtUtil.createRefreshToke(account.getId(),account.getUserName(),
                jwtId,date,authProperties);
    }

    @Override
    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException ex, ServletRequest request, ServletResponse response) {
        String exception=ex.getClass().getName();
        String result;
        if (UnknownAccountException.class.getName().equals(exception)) {
            result= "账号不存在";
        } else if (IncorrectCredentialsException.class.getName().equals(exception)) {
            result="用户或密码错误";
        } else if ("kaptchaValidateFailed".equals(exception)) {
            result="验证码错误";
        } else {
            result=ex.getMessage();
        }
        if(logger.isDebugEnabled()){
            logger.error("登录异常",exception);
        }
        request.setAttribute(LOGIN_FAIL_MESSAGE,result);
        return true;
    }

    public void setAuthProperties(AuthProperties authProperties) {
        this.authProperties = authProperties;
    }

    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        if (authProperties.isJwtMode()){
            HttpServletResponse httpResponse = (HttpServletResponse) response;
            HttpServletRequest httpRequest = (HttpServletRequest) request;
            if (httpRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
                RequestUtil.setHeader(httpRequest,httpResponse);
                return false;
            }
        }
        return super.preHandle(request,response);
    }

    public void setTokenService(AuthTokenService tokenService) {
        this.tokenService = tokenService;
    }
}
